﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents a mathematical vector with 2 components. It should be primary used
    /// for geometric problems in a three dimensional space.
    /// </summary>
    [Serializable]
    public class Vector3D : IVector3D
    {
        /// <summary>
        /// The scale value of the vector.
        /// </summary>
        private double scaleValue;

        /// <summary>
        /// The position vector component of the vector.
        /// </summary>
        private GeneralVector3D positionVector;

        /// <summary>
        /// The direction vector component of the vector.
        /// </summary>
        private GeneralVector3D directionVector;

        /// <summary>
        /// Initializes a new instance of the <see cref="Vector3D"/> class.
        /// </summary>
        /// <param name="positionVector">The position vector component of the vector.</param>
        /// <param name="directionVector">The direction vector component of the vector.</param>
        public Vector3D(GeneralVector3D positionVector, GeneralVector3D directionVector)
        {
            if (positionVector == (GeneralVector3D) null)
            {
                throw new ArgumentNullException("positionVector");
            }

            if (directionVector == (GeneralVector3D) null)
            {
                throw new ArgumentNullException("directionVector");
            }

            this.scaleValue = 1;
            this.positionVector = positionVector;
            this.directionVector = directionVector;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Vector3D"/> class.
        /// </summary>
        /// <param name="positionVector">The position vector component of the vector.</param>
        /// <param name="directionVector">The direction vector component of the vector.</param>
        /// <param name="scaleValue">The scale value of the direction vector component.</param>
        public Vector3D(GeneralVector3D positionVector, GeneralVector3D directionVector, double scaleValue)
        {
            if (positionVector == (GeneralVector3D) null)
            {
                throw new ArgumentNullException("positionVector");
            }

            if (directionVector == (GeneralVector3D) null)
            {
                throw new ArgumentNullException("directionVector");
            }

            this.scaleValue = scaleValue;
            this.positionVector = positionVector;
            this.directionVector = directionVector;
        }

        /// <summary>
        /// Gets or sets the scale value of the direction vector component.
        /// </summary>
        /// <value>The scale value of the direction vector component.</value>
        public double ScaleValue
        {
            get { return scaleValue; }
            set { scaleValue = value; }
        }

        /// <summary>
        /// Gets or sets the position vector component of the vector.
        /// </summary>
        /// <value>The position vector component of the vector.</value>
        public GeneralVector3D PositionVector
        {
            get { return positionVector; }
            set { positionVector = value; }
        }

        /// <summary>
        /// Gets or sets the direction vector component of the vector.
        /// </summary>
        /// <value> The direction vector component of the vector.</value>
        public GeneralVector3D DirectionVector
        {
            get { return directionVector; }
            set { directionVector = value; }
        }

        /// <summary>
        /// Computes the intercept of two vectors in a three dimensional space.
        /// </summary>
        /// <param name="point">The point to which the intercept should be calculated.</param>
        /// <returns>The intercept of two vectors in a three dimensional space.</returns>
        public GeneralVector3D Intercept(Vector3D point)
        {
            if (point == (Vector3D) null)
            {
                throw new ArgumentNullException("point");
            }

            Matrix solveMatrix = new Matrix(2, 3);

            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 0), this.directionVector.X);
            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 1), -point.DirectionVector.X);
            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 2), point.PositionVector.X - this.positionVector.X);

            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 0), this.directionVector.Y);
            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 1), -point.DirectionVector.Y);
            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 2), point.PositionVector.Y - this.positionVector.Y);

            double[] scaleResult = solveMatrix.SolveLinearSystem();

            return (new GeneralVector3D(this.directionVector) * scaleResult[0]) + this.positionVector;
        }

        /// <summary>
        /// Determines whether the current vector [has intercept with] [the specified vector].
        /// </summary>
        /// <param name="point">The vector to which should be checked.</param>
        /// <returns>
        /// 	<c>true</c> if the current vector [has intercept with] [the specified vector]; otherwise, <c>false</c>.
        /// </returns>
        public bool HasInterceptWith(Vector3D point)
        {
            if (point == (Vector3D) null)
            {
                throw new ArgumentNullException("point");
            }

            Matrix solveMatrix = new Matrix(2, 3);

            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 0), this.directionVector.X);
            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 1), -point.DirectionVector.X);
            solveMatrix.SetValueAtPosition(new MatrixPosition(0, 2), point.PositionVector.X - this.positionVector.X);

            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 0), this.directionVector.Y);
            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 1), -point.DirectionVector.Y);
            solveMatrix.SetValueAtPosition(new MatrixPosition(1, 2), point.PositionVector.Y - this.positionVector.Y);

            double[] scaleResult = solveMatrix.SolveLinearSystem();

            bool test1 = (this.positionVector.X + this.directionVector.X * scaleResult[0] ==
                          point.PositionVector.X + point.DirectionVector.X * scaleResult[1]);
            bool test2 = (this.positionVector.Y + this.directionVector.Y * scaleResult[0] ==
                          point.PositionVector.Y + point.DirectionVector.Y * scaleResult[1]);
            bool test3 = (this.positionVector.Z + this.directionVector.Z * scaleResult[0] ==
                          point.PositionVector.Z + point.DirectionVector.Z * scaleResult[1]);

            return test1 && test2 && test3;
        }

        /// <summary>
        /// Multiplies the direction vector with the specified scale value and adds the position vector. 
        /// </summary>
        /// <returns>The result of the calculation in form of a GeneralVector</returns>
        public GeneralVector3D ToGeneralVector()
        {
            return (new GeneralVector3D(this.DirectionVector) * this.scaleValue) + this.positionVector;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">The first vector.</param>
        /// <param name="b">The second vector.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(Vector3D a, Vector3D b)
        {
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object) a == null) || ((object) b == null))
            {
                return false;
            }

            return ((a.DirectionVector == b.DirectionVector) && (a.PositionVector == b.PositionVector)
                    && (a.ScaleValue == b.ScaleValue));
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">The first vector.</param>
        /// <param name="b">The second vector.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(Vector3D a, Vector3D b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return "(" + this.positionVector.ToString() + ") + " + this.scaleValue + "(" +
                   this.directionVector.ToString() + ")";
        }
    }
}